﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Spun.Utility.Locking;
using System.Security.Cryptography;
using System.Threading;
using Spun.Utility;

namespace Spun.Applications
{
    public class ApplicationContextManager
    {
        public ApplicationContextManager(IFactory<ApplicationContext> ApplicationContextFactory)
        {
            this.ApplicationContextFactory = ApplicationContextFactory;

            TokenManager = new TokenManager();
            LockingContext = new LockingContext();
            ControllerContextMap = new Dictionary<ulong, ApplicationContext>();
            RNG = new RNGCryptoServiceProvider();
        }

        public ApplicationContext this[Token Token]
        {
            get
            {
                if (TokenManager.ValidateToken(Token) == false)
                    throw new CannotAccessApplicationContext("the token was invalid");

                ApplicationContext ControllerContext;
                using (LockingContext.ReadLock)
                    if (ControllerContextMap.TryGetValue(Token.Key, out ControllerContext))
                        return ControllerContext;
                    else
                        throw new CannotAccessApplicationContext("no application was found having the ID " + Token.Key);
            }
        }

        public ApplicationContext CreateApplicationContext()
        {
            try
            {
                using (LockingContext.WriteLock)
                {
                    uint NewId = GetNewId();

                    var Token = new Token(NewId);
                    TokenManager.SignToken(Token);

                    var Context = ApplicationContextFactory.New();
                    Context.Token = Token;

                    ControllerContextMap[NewId] = Context;
                    return Context;
                }
            }
            catch (Exception e)
            {
                throw new CouldNotCreateApplicationContext(e);
            }
        }

        private uint GetNewId()
        {
            uint? NewId = null;

            for (int i = 0; i < 100; i++)
            {
                NewId = RandomId();

                if (ControllerContextMap.ContainsKey(NewId.Value) == false)
                    break;

                NewId = null;
            }

            if (NewId.HasValue)
                return NewId.Value;
            else
                throw new CouldNotCreateApplicationContext("ID generation failed");
        }

        private uint RandomId()
        {
            byte[] Buffer = new byte[sizeof(uint)];
            RNG.GetBytes(Buffer);

            uint Value = 0;
            foreach (byte b in Buffer)
                Value = (Value << 8) + (uint)b;

            return Value;
        }

        private IFactory<ApplicationContext> ApplicationContextFactory;
        private RNGCryptoServiceProvider RNG;
        private LockingContext LockingContext;
        private TokenManager TokenManager;
        private IDictionary<ulong, ApplicationContext> ControllerContextMap;
    }
}
